"""
Created on 2022-04-01
@author:刘飞
@description:
框架搭建
它应该至少有3个函数：
初始化函数——设定输入层节点、隐藏层节点和输出层节点的数量。
训练——学习给定训练集样本后，优化权重。
查询——给定输入，从输出节点给出答案。
"""
import numpy as np
from scipy import special


class NeuralNetwork:
    def __init__(self, input_nodes, hidden_nodes, output_nodes, learning_rate):
        """
        初始化神经网络
        :param input_nodes: 输入层节点数
        :param hidden_nodes: 隐藏层节点数
        :param output_nodes: 输出层节点数
        :param learning_rate: 学习率
        """
        self.in_nodes = input_nodes
        self.h_nodes = hidden_nodes
        self.o_nodes = output_nodes
        self.lr = learning_rate
        # 权重矩阵
        # self.wih = (np.random.rand(self.h_nodes, self.in_nodes) - 0.5)
        # self.who = (np.random.rand(self.o_nodes, self.h_nodes) - 0.5)
        # 使用下一层的节点数的开方作为标准方差来初始化权重
        self.wih = np.random.normal(0.0, pow(self.h_nodes, -0.5),
                                    (self.h_nodes, self.in_nodes))
        self.who = np.random.normal(0.0, pow(self.o_nodes, -0.5),
                                    (self.o_nodes, self.h_nodes))
        # 激活函数是s形函数。
        self.activation_function = lambda x: special.expit(x)

    def train(self, inputs_list, targets_list):
        """
        训练神经网络
        :param inputs_list:
        :param targets_list:
        :return:
        """
        # 将输入列表转换为2d数组
        inputs = np.array(inputs_list, ndmin=2).T
        targets = np.array(targets_list, ndmin=2).T

        # 计算信号到隐藏层
        hidden_inputs = np.dot(self.wih, inputs)
        # 计算出隐层信号
        hidden_outputs = self.activation_function(hidden_inputs)

        # 计算信号到最后的输出层
        final_inputs = np.dot(self.who, hidden_outputs)
        # 计算从最后输出层出现的信号
        final_outputs = self.activation_function(final_inputs)
        # 输出层误差是(目标-实际)(target - actual)
        output_errors = targets - final_outputs
        # 隐含层错误是output_errors，按权重分割，在隐含节点上重组
        hidden_errors = np.dot(self.who.T, output_errors)

        # 更新隐藏层和输出层之间链接的权重
        self.who += self.lr * np.dot((output_errors *
                                      final_outputs * (1.0 - final_outputs)),
                                     np.transpose(hidden_outputs))

        # 更新输入层和隐藏层之间链接的权重
        self.wih += self.lr * np.dot((hidden_errors *
                                      hidden_outputs * (1.0 - hidden_outputs)), np.transpose
                                     (inputs))

    def query(self, input_list):
        """
        查询神经网络
        :param input_list:
        :return:
        """
        # 将输入列表转换为2d数组
        inputs = np.array(input_list, ndmin=2).T
        # 计算信号到隐藏层
        hidden_inputs = np.dot(self.wih, inputs)
        # 计算出隐层信号
        hidden_outputs = self.activation_function(hidden_inputs)
        # 计算信号到最后的输出层
        final_inputs = np.dot(self.who, hidden_outputs)
        # 计算从最后输出层出现的信号
        final_outputs = self.activation_function(final_inputs)
        return final_outputs


# 创建每层3个节点、学习率为0.5的小型神经网络对象。

in_nodes = 3
h_nodes = 3
o_nodes = 3
lr = 0.5

n = NeuralNetwork(in_nodes, h_nodes, o_nodes, lr)
res = n.query([1.0, 0.5, -1.5])
print(res)
